﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Globalization;
using System.Security.Cryptography;
using LumiSoft.Net.Mime;
using System.ComponentModel;
using System.Xml.Linq;
using Elentok.Common;
using AMail.Common.Interfaces.Data;
using AMail.Common;
using System.Collections.ObjectModel;
using Elentok.Common.Helpers;

namespace AMail.Modules.Data.Internals
{
  internal class MailMessage : IInternalMailMessage
  {
    #region Private Data Members
    private Mime mime;
    private string subject;
    private IMailFolder parentFolder;
    private IMailFolder previousParentFolder;
    private Encoding encoding;
    private byte[] source;
    private bool isNew;
    private ReadOnlyCollection<IMailAttachment> attachments;
    #endregion

    #region Ctors
    public MailMessage(byte[] data)
    {
      this.Id = data.GenerateMD5();
      this.IsNew = true;
      try
      {
        this.mime = Mime.Parse(data);
      }
      catch (Exception ex)
      {
        throw new InvalidEmlException("Invalid eml file", ex);
      }

      this.source = data;
      this.Subject = mime.MainEntity.Subject;

      if (!string.IsNullOrEmpty(mime.MainEntity.ContentType_CharSet))
      {
        if (mime.MainEntity.ContentType_CharSet.Equals("utf8", StringComparison.InvariantCultureIgnoreCase))
          this.Encoding = Encoding.UTF8;
        else
          this.Encoding = Encoding.GetEncoding(mime.MainEntity.ContentType_CharSet);
      }

      if (mime.MainEntity.From != null)
        this.From = mime.MainEntity.From.ToAddressListString();
      if (mime.MainEntity.To != null)
        this.To = mime.MainEntity.To.ToAddressListString();
      this.Date = mime.MainEntity.Date;
      this.Size = data.Length;
    }

    public MailMessage(XElement element)
    {
      this.IsNew = false;
      this.Id = element.GetStringAttribute("Id");
      this.Subject = element.GetStringAttribute("Subject");
      this.Size = element.GetNumberAttribute("Size");
      this.From = element.GetStringAttribute("From");
      this.To = element.GetStringAttribute("To");
      this.Date = element.GetDateTimeAttribute("Date");

      string encodingAsString = element.GetStringAttribute("Encoding", null);
      if (!string.IsNullOrEmpty(encodingAsString))
        this.Encoding = Encoding.GetEncoding(encodingAsString);
    }
    #endregion

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    /// Raises the PropertyChanged event
    /// </summary>
    protected void OnPropertyChanged(string propertyName)
    {
      PropertyChangedEventHandler handler = PropertyChanged;
      if (handler != null)
        handler(this, new PropertyChangedEventArgs(propertyName));
    }

    #endregion

    #region INotifyPropertyChanging Members

    public event PropertyChangingEventHandler PropertyChanging;

    /// <summary>
    /// Raises the PropertyChanged event
    /// </summary>
    protected void RaisePropertyChanging(string propertyName)
    {
      PropertyChangingEventHandler handler = PropertyChanging;
      if (handler != null)
        handler(this, new PropertyChangingEventArgs(propertyName));
    }

    #endregion

    #region IMailMessage Members

    public string Id { get; private set; }
    public DateTime Date { get; private set; }
    public string From { get; private set; }
    public string To { get; private set; }
    public int Size { get; private set; }

    /// <summary>
    /// Returns new if this message is new and wasn't saved yet
    /// </summary>
    public bool IsNew
    {
      get
      {
        return this.isNew;
      }
      private set
      {
        if (this.isNew == value)
          return;

        this.isNew = value;
        OnPropertyChanged("IsNew");
      }
    }
    public bool IsLoaded
    {
      get
      {
        return this.source != null;
      }
    }

    private Mime Mime
    {
      get
      {
        if (this.mime != null)
          return this.mime;

                // when accessing the "Source" property it will try to
        // load the source from the file, if its not successful
        // the source will stay null
        if (Source == null)
          return null;

        lock (this)
        {
          this.mime = Mime.Parse(source);
        }

        return this.mime;
      }
    }

    /// <summary>
    /// Gets the attachments of this message
    /// </summary>
    public ReadOnlyCollection<IMailAttachment> Attachments
    {
      get
      {
        if (this.attachments != null)
          return this.attachments;

        if (this.Mime == null)
          return null;

        lock (this)
        {
          GenerateAttachmentsFromMime();
        }

        return this.attachments;
      }
    }

    public IMailFolder ParentFolder
    {
      get
      {
        return parentFolder;
      }
      set
      {
        if (this.parentFolder == value)
          return;

        this.PreviousParentFolder = this.parentFolder;
        this.parentFolder = value;

        #region Remove from old parent
        if (this.PreviousParentFolder != null && this.PreviousParentFolder.Messages.Contains(this))
          this.PreviousParentFolder.Messages.Remove(this);
        #endregion

        #region Add to new parent
        if (parentFolder != null && !parentFolder.Messages.Contains(this))
          parentFolder.Messages.Add(this);
        #endregion

        OnPropertyChanged("ParentFolder");
      }
    }

    /// <summary>
    /// Gets or sets the PreviousParentFolder
    /// </summary>
    public IMailFolder PreviousParentFolder
    {
      get
      {
        return this.previousParentFolder;
      }
      private set
      {
        if (this.previousParentFolder == value)
          return;

        this.previousParentFolder = value;
        OnPropertyChanged("PreviousParentFolder");
      }
    }
    public string Subject
    {
      get
      {
        return subject;
      }
      set
      {
        if (subject == value)
          return;

        RaisePropertyChanging("Subject");
        subject = value;
        OnPropertyChanged("Subject");
      }
    }
    public Encoding Encoding
    {
      get
      {
        //if (this.encoding == null)
        //{
        //  if (this.Mime != null)
        //  {
        //    if (!string.IsNullOrEmpty(this.Mime.MainEntity.ContentType_CharSet))
        //    {
        //      if (this.Mime.MainEntity.ContentType_CharSet.ToLowerInvariant() == "utf8")
        //        this.encoding = Encoding.UTF8;
        //      else
        //        this.Encoding = Encoding.GetEncoding(this.Mime.MainEntity.ContentType_CharSet);
        //    }
        //  }
        //}

        //if (this.encoding == null)
        //  this.Encoding = Encoding.UTF8;

        return this.encoding;
      }
      set
      {
        if (encoding == value)
          return;

        RaisePropertyChanging("Encoding");
        encoding = value;
        OnPropertyChanged("Encoding");
      }
    }

    /// <summary>
    /// Gets or sets the Source
    /// </summary>
    public byte[] Source
    {
      get
      {
        if (this.source != null)
          return this.source;

        if (ParentFolder == null || ParentFolder.OwnerArchive == null)
          return null;

        lock (this)
        {
          this.source = ParentFolder.OwnerArchive.ExtractMessageSource(this);
        }

        return this.source;
      }
    }

    public void WriteXml(XmlTextWriter writer)
    {
      writer.WriteStartElement("Message");
      writer.WriteAttributeString("Id", Id);
      writer.WriteAttributeString("Subject", Subject);
      writer.WriteAttributeString("Size", Size.ToString());
      writer.WriteAttributeString("From", From);
      writer.WriteAttributeString("To", To);
      writer.WriteAttributeString("Date", Date.ToString(CultureInfo.InvariantCulture));
      if (this.encoding != null)
        writer.WriteAttributeString("Encoding", this.encoding.WebName);
        
      writer.WriteEndElement();
    }

    public IMailMessage Clone()
    {
      IMailMessage newMessage = new MailMessage(Source);
      newMessage.Subject = Subject;
      newMessage.Encoding = Encoding;
      return newMessage;
    }

    #endregion

    #region IInternalMailMessage Members

    void IInternalMailMessage.MarkAsSaved()
    {
      this.IsNew = false;
    }

    #endregion

    #region IActionableObject Members

    public event EventHandler<MailActionEventArgs> ActionPerformed;

    protected void RaiseActionPerformed(IMailAction action)
    {
      var handler = ActionPerformed;
      if (handler != null)
        handler(this, new MailActionEventArgs(action));
    }

    #endregion

    #region Helper Methods

    private void GenerateAttachmentsFromMime()
    {
      List<IMailAttachment> attachments = new List<IMailAttachment>();

      foreach (var entity in this.mime.MimeEntities)
      {
        var attachment = new MailAttachment(entity, this);
        byte[] data = attachment.Data.ToArray();
        if (data != null && data.Length > 0)
          attachments.Add(attachment);
      }

      this.attachments = attachments.AsReadOnly();
    }

    public override string ToString()
    {
      return string.Format("Message: '{0}'", this.Subject);
    }

    #endregion
  }

  internal interface IInternalMailMessage : IMailMessage
  {
    void MarkAsSaved();
  }
}
